using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Ioc.Kernel.Exceptions;
using Ioc.Kernel.LifeStyles;
using Ioc.Kernel.Selectors;

namespace Ioc.Kernel
{
    public class ComponentModel : IDisposable
    {
        #region Properties
        
        public string Name { get; set; }
        public Type ClassName { get; set; }
        public Type ServiceName { get; set; }
        public IList<Constructor> Constructors { get; set; }
        public Constructor ConstructorCandidate { get; set; }
        public IList<Parameter> Parameters { get; set; }
        public IDictionary<ContainerKey, ComponentModel> Dependencies { get; set; }
        private IComponentSelector componentSelector;
        private IParameterSelector parameterSelector;
        private object primitiveTypeValue;
        public bool IsPrimitive { get; set; }

        public ILifeStyleStrategy lifestyleStrategy;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ComponentModel"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="className">Name of the class.</param>
        /// <param name="serviceName">Name of the service.</param>
        /// <param name="lifestyleStrategy">The lifecycle strategy.</param>
        public ComponentModel(string name, Type className, Type serviceName, ILifeStyleStrategy lifestyleStrategy)
        {
            Name = name;
            ClassName = className;
            ServiceName = serviceName;
            IsPrimitive = false;
            this.lifestyleStrategy = lifestyleStrategy;

            Constructors = null;
            Dependencies = null;
            Parameters = new List<Parameter>();
            componentSelector = null;
            parameterSelector = null;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ComponentModel"/> class.
        /// </summary>
        /// <param Name="name">The Name.</param>
        /// <param Name="ClassName">Name of the class.</param>
        /// <param Name="ServiceName">Name of the service.</param>
        public ComponentModel(string name, Type className, Type serviceName)
            : this(name, className, serviceName, new TransientLifeStyleStrategy()) {}

        #endregion


        /// <summary>
        /// Generates the Constructor.
        /// </summary>
        public void GenerateConstructor(IDictionary<ContainerKey, ComponentModel> components)
        {
            if (ConstructorCandidate == null)
            {
                Constructors = SetConstructor();
                componentSelector = new ComponentSelector(components);
                Dependencies = SetDependencies(components);
            }
        }

        /// <summary>
        /// Sets the dependencies.
        /// </summary>
        /// <returns></returns>
        private IDictionary<ContainerKey, ComponentModel> SetDependencies(IDictionary<ContainerKey, ComponentModel> components)
        {
            var dependencies = new Dictionary<ContainerKey, ComponentModel>();
            if (IsPrimitive)
                return dependencies;

            foreach (var constructor in Constructors)
            {
                dependencies = new Dictionary<ContainerKey, ComponentModel>();
                bool CandidateIncontrated = true;
                foreach (var constructorParameters in constructor.GetConstructor.GetParameters())
                {
                    if (parameterSelector == null)
                        parameterSelector = new ParameterSelector(Parameters);

                    if(! AddDependency(dependencies, constructorParameters, components, constructorParameters.ParameterType))
                    {
                        CandidateIncontrated = false;
                        break;
                    }
                }

                if (CandidateIncontrated)
                {
                    ConstructorCandidate = constructor;
                    break;
                }
            }

            if (ConstructorCandidate == null)
                throw new UnresolvedDependencyException(Name);
            
            return dependencies;
        }

        /// <summary>
        /// Adds the dependency. Returns false if the dependency an't be resolved
        /// </summary>
        /// <param name="dependencies">The dependencies.</param>
        /// <param name="constructorParameters">The constructor parameters.</param>
        /// <param name="components">The components.</param>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        private bool AddDependency(IDictionary<ContainerKey, ComponentModel> dependencies, ParameterInfo constructorParameters, IDictionary<ContainerKey, ComponentModel> components, Type type)
        {
            ComponentModel Dependency;
            ContainerKey containerKey;

            // Searching dependency first in the parameters, and then in the container
            var parameter = parameterSelector.SelectParameter(constructorParameters.Name);
            if (parameter != null)
            {
                Dependency = GetComponent(components, parameter, type);
                if (Dependency == null)
                    return false;
                containerKey = CreateContainerKey(Dependency);
            }
            else
            {
                containerKey = componentSelector.GetComponent(type, string.Empty);
                if (containerKey == null)
                    return false;
                Dependency = components[containerKey];
            }

            dependencies.Add(containerKey, Dependency);
            return true;
        }

        /// <summary>
        /// Creates the container key.
        /// </summary>
        /// <param name="dependency">The dependency.</param>
        /// <returns></returns>
        private static ContainerKey CreateContainerKey(ComponentModel dependency)
        {
            return new ContainerKey(dependency.Name, dependency.ServiceName);
        }

        /// <summary>
        /// Adds the parameters.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        public void AddParameters(Parameter parameter)
        {
            Parameters.Add(parameter);
        }

        /// <summary>
        /// Gets the component.
        /// </summary>
        /// <param name="components">The components.</param>
        /// <param name="parameter">The parameter.</param>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        private ComponentModel GetComponent(IDictionary<ContainerKey, ComponentModel> components, Parameter parameter, Type type)
        {
            if (parameter.IsComponentReference())
            {
                var containerKey = componentSelector.GetComponent(type, parameter.EscapedValue);
                return containerKey == null ? null : components[containerKey];
            }

            var component = new ComponentModel(parameter.Name, type, type)
                                {
                                    IsPrimitive = true,
                                    primitiveTypeValue = parameter.Value
                                };
            return component;
        }

        /// <summary>
        /// Sets the Constructor.
        /// </summary>
        /// <returns></returns>
        private IList<Constructor> SetConstructor()
        {
            var constructorList = new List<Constructor>();
            foreach (var constructorInfo in ClassName.GetConstructors())
            {
                constructorList.Add(new Constructor(constructorInfo));
            }
            constructorList.Sort();
            return constructorList;
        }

        /// <summary>
        /// Gets the expression.
        /// </summary>
        public Expression GetExpression()
        {
            if (IsPrimitive)
                return Expression.Constant(Convert.ChangeType(primitiveTypeValue, ClassName));

            if (Constructors == null)
                throw new UninitializedComponentException(Name);

            if (!lifestyleStrategy.MustInstanciate())
                return lifestyleStrategy.GetExpression(ClassName);

            Expression expression = Dependencies.Count == 0
                                        ? Expression.New(ClassName)
                                        : Expression.New(
                                              ClassName.GetConstructor(ConstructorCandidate.GenerateSignature()),
                                              GetConstructorDepencies());

            lifestyleStrategy.SetExpression(expression);
            return lifestyleStrategy.GetExpression(ClassName) ?? expression;
        }

        /// <summary>
        /// Gets the constructor depencies.
        /// </summary>
        /// <returns></returns>
        private List<Expression> GetConstructorDepencies()
        {
            var arguments = new List<Expression>();
            foreach (var component in Dependencies.Values)
            {
                arguments.Add(component.GetExpression());
            }
            return arguments;
        }

        /// <summary>
        /// Creates the component.
        /// </summary>
        /// <returns></returns>
        public object CreateComponent()
        {
            var func = Expression.Lambda<Func<object>>(GetExpression()).Compile();
            return func();
        }

        #region Equals

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof (ComponentModel)) return false;
            return Equals((ComponentModel) obj);
        }

        public bool Equals(ComponentModel obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return Equals(obj.Name, Name) && Equals(obj.ClassName, ClassName) && Equals(obj.ServiceName, ServiceName);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int result = Name.GetHashCode();
                result = (result*397) ^ ClassName.GetHashCode();
                result = (result*397) ^ ServiceName.GetHashCode();
                return result;
            }
        }

        public static bool operator ==(ComponentModel left, ComponentModel right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(ComponentModel left, ComponentModel right)
        {
            return !Equals(left, right);
        }

        #endregion

        #region Disposing

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected void Dispose(bool disposing)
        {
            if (!disposing) return;

            if(Constructors != null)
                Constructors.Clear();
            if (Dependencies != null)
                Dependencies.Clear();
            Parameters.Clear();
            lifestyleStrategy.Dispose();
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="ComponentModel"/> is reclaimed by garbage collection.
        /// </summary>
        ~ComponentModel()
        {
            Dispose(false);
        }

        #endregion
    }
}